@tool
extends Control

#region 属性
const DebuggerStateInfo = preload("res://addons/State_Chart/util/editor_debugger/editor_debugger_state_info.gd")
const DebuggerHistory = preload("res://addons/State_Chart/util/debugger_history.gd")
const DebuggerMessage = preload("res://addons/State_Chart/util/editor_debugger/editor_debugger_message.gd")
const SETTINGS_ROOT:String = "state_charts/debugger/"
const SETTINGS_IGNORE_EVENTS:String = SETTINGS_ROOT + "ignore_events"
const SETTINGS_IGNORE_STATE_CHANGES:String = SETTINGS_ROOT + "ignore_state_changes"
const SETTINGS_IGNORE_TRANSITIONS:String = SETTINGS_ROOT + "ignore_transitions"
const SETTINGS_MAXIMUM_LINES:String = SETTINGS_ROOT + "maximum_lines"
const SETTINGS_SPLIT_OFFSET:String = SETTINGS_ROOT + "split_offset"
var _ignore_events:bool = true
var _ignore_state_changes:bool = false
var _ignore_transitions:bool = true
var _settings:EditorSettings = null
var _session = null
var _state_infos:Dictionary = {}
var _chart_histories:Dictionary = {}
var _current_chart:NodePath = ""
var _debounced_maximum_lines:int = -1
@onready var _all_state_charts_tree:Tree = %AllStateChartsTree
@onready var _current_state_chart_tree:Tree = %CurrentStateChartTree
@onready var _history_edit:TextEdit = %HistoryEdit
@onready var _ignore_events_checkbox:CheckBox = %IgnoreEventsCheckbox
@onready var _ignore_state_changes_checkbox:CheckBox = %IgnoreStateChangesCheckbox
@onready var _ignore_transitions_checkbox:CheckBox = %IgnoreTransitionsCheckbox
@onready var _maximum_lines_spin_box:SpinBox = %MaximumLinesSpinBox
@onready var _split_container:HSplitContainer = %SplitContainer
#endregion
#region 字段
#endregion
#region 信号
#endregion

#region 基类方法
#endregion
#region 公共方法
func init(setting:EditorSettings,session:EditorDebuggerSession):
	clear()
	_settings = setting
	_session = session
	
	_ignore_events = _get_setting_or_default(SETTINGS_IGNORE_EVENTS,true)
	_ignore_state_changes = _get_setting_or_default(SETTINGS_IGNORE_STATE_CHANGES,false)
	_ignore_transitions = _get_setting_or_default(SETTINGS_IGNORE_TRANSITIONS,true)
	
	_ignore_events_checkbox.set_pressed_no_signal(_ignore_events)
	_ignore_state_changes_checkbox.set_pressed_no_signal(_ignore_state_changes)
	_ignore_transitions_checkbox.set_pressed_no_signal(_ignore_transitions)
	_maximum_lines_spin_box.value = _get_setting_or_default(SETTINGS_MAXIMUM_LINES,300)
	_split_container.split_offset = _get_setting_or_default(SETTINGS_SPLIT_OFFSET,0)
func clear():
	_clear_all()
func add_chart(path:NodePath):
	_state_infos[path] = {}
	_chart_histories[path] = DebuggerHistory.new()
	_repaint_charts()
	DebuggerMessage.settings_updated(_session,path,_ignore_events,_ignore_transitions)
	if _current_chart.is_empty():
		_current_chart = path
		_select_current_chart()
func remove_chart(path:NodePath):
	_state_infos.erase(path)
	if _current_chart == path:
		_clear_current()
	_repaint_charts()
func update_state(frame:int,state_info:Array):
	var chart = DebuggerStateInfo.get_chart(state_info)
	var path = DebuggerStateInfo.get_state(state_info)
	if not _state_infos.has(chart):
		push_error("可能得BUG：收到未知Chart[P{0}]的State信息".format([chart]))
		return
	_state_infos[chart][path] = state_info
func state_entered(frame:int,chart:NodePath,state:NodePath):
	if not _state_infos.has(chart):
		return
	if not _ignore_state_changes:
		var history:DebuggerHistory = _chart_histories[chart]
		history.add_state_entered(frame,_get_node_name(state))
	var state_info = _state_infos[chart][state]
	DebuggerStateInfo.set_active(state_info,true)
func state_exited(frame:int,chart:NodePath,state:NodePath):
	if not _state_infos.has(chart):
		return
	if not _ignore_state_changes:
		var history:DebuggerHistory = _chart_histories[chart]
		history.add_state_exited(frame,_get_node_name(state))
	var state_info = _state_infos[chart][state]
	DebuggerStateInfo.set_active(state_info,false)
func event_received(frame:int,chart:NodePath,event:StringName):
	var history:DebuggerHistory = _chart_histories.get(chart,null)
	history.add_event(frame,event)
func transition_pending(frame:int,chart:NodePath,state:NodePath,transition:NodePath,pending_time:float):
	var state_info = _state_infos[chart][state]
	DebuggerStateInfo.set_transition_pending(state_info,transition,pending_time)
func transition_taken(frame:int,chart:NodePath,transition:NodePath,source:NodePath,destination:NodePath):
	var history:DebuggerHistory = _chart_histories.get(chart,null)
	history.add_transition(frame,_get_node_name(transition),_get_node_name(source),_get_node_name(destination))
#endregion
#region 私有方法
func _get_setting_or_default(key,default) -> Variant:
	if not _settings:
		return default
	if not _settings.has_setting(key):
		return default
	return _settings.get_setting(key)
func _set_setting(key,value):
	if _settings == null:
		return
	_settings.set_setting(key,value)
	_settings.mark_setting_changed(key)
func _clear_all():
	_state_infos.clear()
	_chart_histories.clear()
	_all_state_charts_tree.clear()
	
	var root = _all_state_charts_tree.create_item()
	root.set_text(0,"State Charts")
	root.set_selectable(0,false)
	
	_clear_current()
func _clear_current():
	_current_chart = ""
	_current_state_chart_tree.clear()
	_history_edit.clear()
	var root = _current_state_chart_tree.create_item()
	root.set_text(0,"States")
	root.set_selectable(0,false)
func _repaint_charts():
	for chart in _state_infos.keys():
		_add_to_tree(_all_state_charts_tree,chart,preload("res://addons/State_Chart/icons/state_chart.svg"))
	_clear_unused_items(_all_state_charts_tree.get_root())
func _select_current_chart(item:TreeItem = _all_state_charts_tree.get_root()):
	if item.has_meta("__path") and item.get_meta("__path") == _current_chart:
		_all_state_charts_tree.set_selected(item,0)
		return
	var first_child = item.get_first_child()
	if first_child != null:
		_select_current_chart(first_child)
	var next = item.get_next()
	while next != null:
		_select_current_chart(next)
		next = next.get_next()
func _repaint_current_chart(force:bool = false):
	if _current_chart.is_empty():
		return
	var history:DebuggerHistory = _chart_histories[_current_chart]
	if history != null and (history.dirty or force):
		_history_edit.text = history.get_history_text()
		_history_edit.scroll_vertical = _history_edit.get_line_count() - 1
	for state_info in _state_infos[_current_chart].values():
		if DebuggerStateInfo.get_active(state_info):
			_add_to_tree(_current_state_chart_tree,DebuggerStateInfo.get_state(state_info),DebuggerStateInfo.get_state_icon(state_info))
		if DebuggerStateInfo.get_transition_pending(state_info):
			var transition_path = DebuggerStateInfo.get_transition_path(state_info)
			var transition_delay = DebuggerStateInfo.get_transition_delay(state_info)
			var name = _get_node_name(transition_path)
			_add_to_tree(_current_state_chart_tree,DebuggerStateInfo.get_transition_path(state_info),preload("res://addons/State_Chart/icons/transition.svg"),"%s (%.1fs)" % [name, transition_delay])
	_clear_unused_items(_current_state_chart_tree.get_root())
func _clear_unused_items(root:TreeItem):
	if root == null:
		return
	for child in root.get_children():
		if not child.has_meta("__in_use"):
			root.remove_child(child)
			_free_all(child)
		else:
			child.remove_meta("__in_use")
			_clear_unused_items(child)
func _free_all(root:TreeItem):
	if root == null:
		return
	for child in root.get_children():
		root.remove_child(child)
		_free_all(child)
	root.free()
func _add_to_tree(tree:Tree,path:NodePath,icon:Texture2D,text:String = ""):
	var ref = tree.get_root()
	for i in path.get_name_count():
		var segment = path.get_name(i)
		var needs_new = true
		if ref != null:
			for child in ref.get_children():
				if child.get_text(0) == segment:
					ref = child
					ref.set_meta("__in_use",true)
					needs_new = false
					break
		if needs_new:
			ref = tree.create_item(ref)
			ref.set_text(0,segment)
			ref.set_meta("__in_use",true)
			ref.set_selectable(0,false)
	ref.set_meta("__path",path)
	if text != "":
		ref.set_text(0,text)
	ref.set_icon(0,icon)
	ref.set_selectable(0,true)
func _on_all_state_charts_tree_item_selected():
	var item = _all_state_charts_tree.get_selected()
	if item == null:
		return
	if not item.has_meta("__path"):
		return
	var path = item.get_meta("__path")
	_current_chart = path
	_repaint_current_chart(true)
func _on_timer_timeout():
	if _debounced_maximum_lines >= 0:
		_set_setting(SETTINGS_MAXIMUM_LINES,_debounced_maximum_lines)
		for history in _chart_histories.values():
			history.set_maximum_lines(_debounced_maximum_lines)
		_debounced_maximum_lines = -1
	_repaint_current_chart()
func _on_ignore_events_checkbox_toggled(button_pressed:bool):
	_ignore_events = button_pressed
	_set_setting(SETTINGS_IGNORE_EVENTS,button_pressed)
	for chart in _state_infos.keys():
		DebuggerMessage.settings_updated(_session,chart,_ignore_events,_ignore_transitions)
func _on_ignore_state_changes_checkbox_toggled(button_pressed:bool):
	_ignore_state_changes = button_pressed
	_set_setting(SETTINGS_IGNORE_STATE_CHANGES,button_pressed)
func _on_ignore_transitions_checkbox_toggled(button_pressed:bool):
	_ignore_transitions = button_pressed
	_set_setting(SETTINGS_IGNORE_TRANSITIONS,button_pressed)
	for chart in _state_infos.keys():
		DebuggerMessage.settings_updated(_session,chart,_ignore_events,_ignore_transitions)
func _on_maximum_lines_spin_box_value_changed(value:int):
	_debounced_maximum_lines = value
func _on_split_container_dragged(offest:int):
	_set_setting(SETTINGS_SPLIT_OFFSET,offest)
func _get_node_name(path:NodePath) -> StringName:
	return path.get_name(path.get_name_count() - 1)
func _on_clear_button_pressed():
	_history_edit.text = ""
	if _chart_histories.has(_current_chart):
		var history:DebuggerHistory = _chart_histories[_current_chart]
		history.clear()
func _on_copy_to_clipboard_button_pressed():
	DisplayServer.clipboard_set(_history_edit.text)
#endregion
#region 生命周期函数
#endregion
